package com.jltx.storage.downloader.net;

import android.util.Pair;


import com.jltx.storage.downloader.FileCallback;
import com.jltx.storage.downloader.MultiRequestResult;
import com.jltx.storage.downloader.RequestException;
import com.jltx.storage.downloader.RequestResult;
import com.jltx.storage.downloader.RequestStatus;
import com.jltx.storage.downloader.StorageDownloader;
import com.jltx.zeus.observable.AsyncMultiCall;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;

/**
 * @author jltxseo
 * Created by junlintianxia on 2019/12/03.
 */
public class DownloadMultiAsyncCall implements AsyncMultiCall<MultiRequestResult> {
    public final List<String> urlList;
    public final List<String> localPathList;
    public final FileCallback fileCallback;
    public Callback<MultiRequestResult> mCallback;

    private Disposable mDisposable;
    public List<RequestResult> successList = new ArrayList<>();

    public List<RequestResult> failList = new ArrayList<>();
    /**
     * 记录所有子任务的进度汇总
     */
    private Map<Object, Float> onProgressMap = new ConcurrentHashMap();
    /**
     * 当前走过的总进度
     */
    private float curProgress = 0f;

    /**
     * 是否取消
     */
    private volatile boolean mCancel = false;

    public DownloadMultiAsyncCall(List<String> urlList, List<String> localPathList, FileCallback fileCallback) {
        this.urlList = urlList;
        this.localPathList = localPathList;
        this.fileCallback = fileCallback;
    }

    @Override
    public void enqueue(Callback<MultiRequestResult> callback) {
        mCallback = callback;
        List<Observable<RequestResult>> observableList = new ArrayList<>();
        for (int i = 0; i < urlList.size(); i++) {
            final String beanUrl = urlList.get(i);
            final String path = localPathList.get(i);
            final Pair<String, String> tag = new Pair<String, String>(beanUrl, path);
            Observable<RequestResult> singleObservable = StorageDownloader.downloadObservable(beanUrl, path)
                    .observeOn(AndroidSchedulers.mainThread())
                    .doOnNext(new Consumer<RequestResult>() {
                        @Override
                        public void accept(RequestResult result) throws Exception {
                            if (result.status == RequestStatus.STATUS_LOADING) {
                                if (fileCallback != null) {
                                    fileCallback.onLoading(tag, result);
                                }
                            } else if (result.status == RequestStatus.STATUS_SUCCESS) {
                                if (fileCallback != null) {
                                    fileCallback.onSuccess(tag, result);
                                }
                                successList.add(result);
                            }

                            if (!isCancel()) {
                                curProgress = Math.max(curProgress, updateSingleTaskForProgress(tag, result.progress * 1.0f / result.total));
                                MultiRequestResult multiRequestResult = new MultiRequestResult(RequestStatus.STATUS_LOADING, getDownloadCount(), curProgress, successList, failList);
                                mCallback.onNext(multiRequestResult);
                            }

                        }
                    })
                    .doOnError(new Consumer<Throwable>() {
                        @Override
                        public void accept(Throwable throwable) throws Exception {
                            RequestResult requestResult = new RequestResult(path, beanUrl, RequestStatus.STATUS_FAIL, RequestException.transformException(throwable));
                            if (fileCallback != null) {
                                fileCallback.onFailure(tag, requestResult);
                            }

                            failList.add(requestResult);

                            if (!isCancel()) {
                                MultiRequestResult multiRequestResult = new MultiRequestResult(RequestStatus.STATUS_LOADING, getDownloadCount(), curProgress, successList, failList);
                                mCallback.onNext(multiRequestResult);
                            }
                        }
                    })
                    .observeOn(Schedulers.io())
                    .takeLast(1);
            observableList.add(singleObservable);
        }
        Observable.mergeDelayError(observableList).toList().toObservable()
                .subscribe(new Observer<List<RequestResult>>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                        mDisposable = d;
                    }

                    @Override
                    public void onNext(List<RequestResult> requestResults) {
                        MultiRequestResult multiRequestResult = new MultiRequestResult(RequestStatus.STATUS_SUCCESS, getDownloadCount(), 1f, requestResults, null);
                        mCallback.onNext(multiRequestResult);
                        mCallback.onComplete();
                    }

                    @Override
                    public void onError(Throwable e) {
                        MultiRequestResult multiRequestResult = new MultiRequestResult(RequestStatus.STATUS_FAIL, getDownloadCount(), curProgress, successList, failList, RequestException.transformException(e));
                        mCallback.onNext(multiRequestResult);
                        mCallback.onComplete();
                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }

    @Override
    public void cancel() {
        mCancel = true;
        if (mDisposable != null) {
            mDisposable.dispose();
        }
    }

    /**
     * 是否取消
     *
     * @return
     */
    public boolean isCancel() {
        return mCancel;
    }

    public int getDownloadCount() {
        return urlList != null ? urlList.size() : 0;
    }

    /**
     * 一个个更新单个进度，并且返回汇总后的总进度
     *
     * @param tag
     * @param progressF
     * @return
     */
    public float updateSingleTaskForProgress(Object tag, float progressF) {
        float totalProgress = 0;
        synchronized (onProgressMap) {
            if (tag != null) {
                onProgressMap.put(tag, progressF);
            }
            Iterator<Map.Entry<Object, Float>> iterator = onProgressMap.entrySet().iterator();

            while (iterator.hasNext()) {
                Map.Entry<Object, Float> entry = iterator.next();
                Float value = entry.getValue();
                totalProgress += (value * (1.0f / getDownloadCount()));
            }
        }
        return totalProgress;
    }
}
